// Decompiled on 周六 2月 22 19:31:49 CST 2025 with Zomboid Decompiler v0.1.3 using Vineflower.
package zombie;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.UUID;
import zombie.core.profiling.TriggerGameProfilerFile;
import zombie.debug.DebugLog;
import zombie.debug.DebugOptions;
import zombie.iso.IsoCamera;
import zombie.ui.TextManager;
import zombie.util.IPooledObject;
import zombie.util.Lambda;
import zombie.util.Pool;
import zombie.util.PooledObject;
import zombie.util.lambda.Invokers;

public final class GameProfiler {
    private static final String s_currentSessionUUID = UUID.randomUUID().toString();
    private static final ThreadLocal<zombie.GameProfiler> s_instance = ThreadLocal.withInitial(zombie.GameProfiler::new);
    private final Stack<zombie.GameProfiler.ProfileArea> m_stack = new Stack();
    private final zombie.GameProfiler.RecordingFrame m_currentFrame = new zombie.GameProfiler.RecordingFrame();
    private final zombie.GameProfiler.RecordingFrame m_previousFrame = new zombie.GameProfiler.RecordingFrame();
    private boolean m_isInFrame;
    private final zombie.GameProfileRecording m_recorder;
    private static final Object m_gameProfilerRecordingTriggerLock = "Game Profiler Recording Watcher, synchronization lock";
    private static zombie.PredicatedFileWatcher m_gameProfilerRecordingTriggerWatcher;

    private GameProfiler() {
        String string0 = Thread.currentThread().getName();
        String string1 = string0.replace("-", "").replace(" ", "");
        String string2 = String.format("%s_GameProfiler_%s", this.getCurrentSessionUUID(), string1);
        this.m_recorder = new zombie.GameProfileRecording(string2);
    }

    private static void onTrigger_setAnimationRecorderTriggerFile(TriggerGameProfilerFile triggerGameProfilerFile) {
        DebugOptions.instance.GameProfilerEnabled.setValue(triggerGameProfilerFile.isRecording);
    }

    private String getCurrentSessionUUID() {
        return s_currentSessionUUID;
    }

    public static zombie.GameProfiler getInstance() {
        return (zombie.GameProfiler)s_instance.get();
    }

    public void startFrame(String frameInvokerKey) {
        if (this.m_isInFrame) {
            throw new RuntimeException("Already inside a frame.");
        } else {
            this.m_isInFrame = true;
            if (!this.m_stack.empty()) {
                throw new RuntimeException("Recording stack should be empty.");
            } else {
                int _int = IsoCamera.frameState.frameCount;
                if (this.m_currentFrame.FrameNo != _int) {
                    this.m_previousFrame.transferFrom(this.m_currentFrame);
                    if (this.m_previousFrame.FrameNo != -1) {
                        this.m_recorder.writeLine();
                    }

                    long _long = getTimeNs();
                    this.m_currentFrame.FrameNo = _int;
                    this.m_currentFrame.m_frameInvokerKey = frameInvokerKey;
                    this.m_currentFrame.m_startTime = _long;
                    this.m_recorder.reset();
                    this.m_recorder.setFrameNumber(this.m_currentFrame.FrameNo);
                    this.m_recorder.setStartTime(this.m_currentFrame.m_startTime);
                }
            }
        }
    }

    public void endFrame() {
        this.m_currentFrame.m_endTime = getTimeNs();
        this.m_currentFrame.m_totalTime = this.m_currentFrame.m_endTime - this.m_currentFrame.m_startTime;
        this.m_isInFrame = false;
    }

    public void invokeAndMeasureFrame(String invokerKey, Runnable invoke) {
        if (!isRunning()) {
            invoke.run();
        } else {
            this.startFrame(invokerKey);

            try {
                this.invokeAndMeasure(invokerKey, invoke);
            } finally {
                this.endFrame();
            }
        }
    }

    public void invokeAndMeasure(String areaKey, Runnable invoke) {
        if (!isRunning()) {
            invoke.run();
        } else if (!this.m_isInFrame) {
            DebugLog.General.warn("Not inside in a frame. Find the root caller function for this thread, and add call to invokeAndMeasureFrame.");
        } else {
            zombie.GameProfiler.ProfileArea profileArea = this.start(areaKey);

            try {
                invoke.run();
            } finally {
                this.end(profileArea);
            }
        }
    }

    public static boolean isRunning() {
        return DebugOptions.instance.GameProfilerEnabled.getValue();
    }

    public <T1> void invokeAndMeasure(String string, T1 object, Invokers.Params1.ICallback<T1> iCallback) {
        if (!isRunning()) {
            iCallback.accept(object);
        } else {
            Lambda.capture(
                this,
                string,
                object,
                iCallback,
                (genericStack, gameProfiler, stringx, objectx, iCallbackx) -> gameProfiler.invokeAndMeasure(stringx, genericStack.invoker(objectx, iCallbackx))
            );
        }
    }

    public <T1, T2> void invokeAndMeasure(String string, T1 object0, T2 object1, Invokers.Params2.ICallback<T1, T2> iCallback) {
        if (!isRunning()) {
            iCallback.accept(object0, object1);
        } else {
            Lambda.capture(
                this,
                string,
                object0,
                object1,
                iCallback,
                (genericStack, gameProfiler, stringx, object0x, object1x, iCallbackx) -> gameProfiler.invokeAndMeasure(
                        stringx, genericStack.invoker(object0x, object1x, iCallbackx)
                    )
            );
        }
    }

    public <T1, T2, T3> void invokeAndMeasure(String string, T1 object0, T2 object1, T3 object2, Invokers.Params3.ICallback<T1, T2, T3> iCallback) {
        if (!isRunning()) {
            iCallback.accept(object0, object1, object2);
        } else {
            Lambda.capture(
                this,
                string,
                object0,
                object1,
                object2,
                iCallback,
                (genericStack, gameProfiler, stringx, object0x, object1x, object2x, iCallbackx) -> gameProfiler.invokeAndMeasure(
                        stringx, genericStack.invoker(object0x, object1x, object2x, iCallbackx)
                    )
            );
        }
    }

    public zombie.GameProfiler.ProfileArea start(String areaKey) {
        long _long = getTimeNs();
        zombie.GameProfiler.ProfileArea profileArea = zombie.GameProfiler.ProfileArea.alloc();
        profileArea.Key = areaKey;
        return this.start(profileArea, _long);
    }

    public zombie.GameProfiler.ProfileArea start(zombie.GameProfiler.ProfileArea area) {
        long _long = getTimeNs();
        return this.start(area, _long);
    }

    public zombie.GameProfiler.ProfileArea start(zombie.GameProfiler.ProfileArea area, long timeNs) {
        area.StartTime = timeNs;
        area.Depth = this.m_stack.size();
        if (!this.m_stack.isEmpty()) {
            zombie.GameProfiler.ProfileArea profileArea = (zombie.GameProfiler.ProfileArea)this.m_stack.peek();
            profileArea.Children.add(area);
        }

        this.m_stack.push(area);
        return area;
    }

    public void end(zombie.GameProfiler.ProfileArea area) {
        area.EndTime = getTimeNs();
        area.Total = area.EndTime - area.StartTime;
        if (this.m_stack.peek() != area) {
            throw new RuntimeException("Incorrect exit. ProfileArea " + area + " is not at the top of the stack: " + this.m_stack.peek());
        } else {
            this.m_stack.pop();
            if (this.m_stack.isEmpty()) {
                this.m_recorder.logTimeSpan(area);
                area.release();
            }
        }
    }

    private void renderPercent(String string, long _long, int int1, int int0, float float3, float float2, float float1) {
        float float0 = (float)_long / (float)this.m_previousFrame.m_totalTime;
        float0 *= 100.0F;
        float0 = (float)((int)(float0 * 10.0F)) / 10.0F;
        TextManager.instance.DrawString((double)int1, (double)int0, string, (double)float3, (double)float2, (double)float1, 1.0);
        TextManager.instance.DrawString((double)(int1 + 300), (double)int0, float0 + "%", (double)float3, (double)float2, (double)float1, 1.0);
    }

    public void render(int x, int y) {
        this.renderPercent(this.m_previousFrame.m_frameInvokerKey, this.m_previousFrame.m_totalTime, x, y, 1.0F, 1.0F, 1.0F);
    }

    public static long getTimeNs() {
        return System.nanoTime();
    }

    public static void init() {
        initTriggerWatcher();
    }

    private static void initTriggerWatcher() {
        if (m_gameProfilerRecordingTriggerWatcher == null) {
            synchronized (m_gameProfilerRecordingTriggerLock) {
                if (m_gameProfilerRecordingTriggerWatcher == null) {
                    m_gameProfilerRecordingTriggerWatcher = new zombie.PredicatedFileWatcher(
                        zombie.ZomboidFileSystem.instance.getMessagingDirSub("Trigger_PerformanceProfiler.xml"),
                        TriggerGameProfilerFile.class,
                        zombie.GameProfiler::onTrigger_setAnimationRecorderTriggerFile
                    );
                    zombie.DebugFileWatcher.instance.add(m_gameProfilerRecordingTriggerWatcher);
                }
            }
        }
    }

    public static class ProfileArea extends PooledObject {
        public String Key;
        public long StartTime;
        public long EndTime;
        public long Total;
        public int Depth;
        public float r = 1.0F;
        public float g = 1.0F;
        public float b = 1.0F;
        public final List<zombie.GameProfiler.ProfileArea> Children = new ArrayList();
        private static final Pool<zombie.GameProfiler.ProfileArea> s_pool = new Pool<>(zombie.GameProfiler.ProfileArea::new);

        @Override
        public void onReleased() {
            super.onReleased();
            this.clear();
        }

        public void clear() {
            this.StartTime = 0L;
            this.EndTime = 0L;
            this.Total = 0L;
            this.Depth = 0;
            IPooledObject.release(this.Children);
        }

        public static zombie.GameProfiler.ProfileArea alloc() {
            return s_pool.alloc();
        }
    }

    public static class RecordingFrame {
        private String m_frameInvokerKey = "";
        private int FrameNo = -1;
        private long m_startTime = 0L;
        private long m_endTime = 0L;
        private long m_totalTime = 0L;

        public void transferFrom(zombie.GameProfiler.RecordingFrame srcFrame) {
            this.clear();
            this.FrameNo = srcFrame.FrameNo;
            this.m_frameInvokerKey = srcFrame.m_frameInvokerKey;
            this.m_startTime = srcFrame.m_startTime;
            this.m_endTime = srcFrame.m_endTime;
            this.m_totalTime = srcFrame.m_totalTime;
            srcFrame.clear();
        }

        public void clear() {
            this.FrameNo = -1;
            this.m_frameInvokerKey = "";
            this.m_startTime = 0L;
            this.m_endTime = 0L;
            this.m_totalTime = 0L;
        }
    }
}
